from re import T
from unittest import main
from tqsdk import TqApi, TqAuth, TqKq, TargetPosScheduler, TqAccount
from tqsdk.algorithm import twap_table

import traceback
import logging
from logging.handlers import SMTPHandler
import time
import json
import os
import pandas as pd

from db_interface import *
pd.set_option('display.max_columns', None)  # dataframe 在专为字符串的时候不要用省略号
pd.set_option('display.width', 1000)  # 打印的一行的宽度为1000， 避免因为设置的不够宽而换行。

# region 配置信息
class Context():
    """全局变量用于存储各种类型的东西"""
    all_data_used_path = '/root/trade/config/all_data_used.txt'
    strtegy_to_run_path = '/root/trade/config/strategytorun.txt'
    current_account_path = '/root/trade/config/currentAccount.csv'
    general_ticker_info_path = '/root/trade/config/general_ticker_info.csv'
    log_path = './log/main.log'
    error_count = {}
    offect_rule = '今昨,开'
    vol_factor = 11000  # 多少钱开一手
    long = 666
    short = 118
    check_mark = True
    main_contract = ''

context = Context()
with open('./config/Record_sma.json') as f:
    cfg = json.load(f)
    context.long = cfg["sma"]["long"]
    context.short = cfg["sma"]["short"]
    context.main_contract = cfg["sma"]["main_contract"]
    context.leicang = cfg["sma"]["leicang"]
    context.vol_factor = cfg["sma"]['vol_factor']

    
logger = logging.getLogger('Yhlz')
# logger.setLevel(logging.DEBUG)
fm = logging.Formatter('%(asctime)s-%(created)f-%(name)s-%(levelname)s-%(lineno)d-%(message)s')

fh = logging.FileHandler(context.log_path)
fh.setFormatter(fm)
fh.setLevel(logging.DEBUG)

mail_handler = SMTPHandler(
        mailhost=('smtp.qq.com',587),
        fromaddr='517353631@qq.com',
        toaddrs='517353631@qq.com',
        subject='sma',
        credentials=('517353631@qq.com', 'fvwmvdtyfexybjea'))
mail_handler.setLevel(logging.WARNING)
mail_handler.setFormatter(fm)

logger.addHandler(mail_handler)
logger.setLevel(logging.DEBUG)
logger.addHandler(fh)
logger.propagate = False
# endregion

def manage_order(symbol, vol):
        global context, position
        # 设置twap任务参数
        max_volume_each_order = 30
        min_volume_each_order = 10
        if symbol in position:
            duration = int(10 * (abs(vol - position[symbol].pos) / (max_volume_each_order + min_volume_each_order)))
            logger.debug(f'{symbol} in position')
        else:
            duration = int(10 * (abs(vol) / (max_volume_each_order + min_volume_each_order)))  # 有正好要平仓，结果其实没有仓位的情况， 这就会导致时间为0
            logger.debug(f'{symbol} not in position')
        duration = max(duration, 3)
        logger.debug(f'下单信息{symbol}, {max_volume_each_order}, {min_volume_each_order}, {duration}, {vol}')
        time_table = twap_table(api, symbol, vol, duration, min_volume_each_order, max_volume_each_order)
        logger.debug(time_table.to_string())

        return TargetPosScheduler(api, symbol, time_table, offset_priority=context.offect_rule)

def check_change_contract(api):
    global kline
    main_contract = cfg['sma']['main_contract']

    contract_map = {"01": "05", "05": "10", "10": "01"}
    temp = contract_map[main_contract[-2:]]
    if temp == "01":  # 新年，前面的十年要就加一
        future_contract = main_contract[:-4] + str(int(main_contract[-4:-2]) + 1) + temp
    else:
        future_contract = main_contract[:-2] + temp
    
    quote_main = api.get_quote(main_contract)
    quote_future = api.get_quote(future_contract)

    if quote_future.open_interest > quote_main.open_interest * 0.7:  #  未来的合约大于现在的合约的持仓量的0.7倍，换合约

        main_kline = api.get_kline_serial(main_contract, 60, data_length=700)
        future_kline = api.get_kline_serial(future_contract, 60, data_length=700)
        main_kline['ma_short'] = main_kline['close'].rolling(context.short).mean()
        main_kline['ma_long'] = main_kline['close'].rolling(context.long).mean()
        future_kline['ma_short'] = future_kline['close'].rolling(context.short).mean()
        future_kline['ma_long'] = future_kline['close'].rolling(context.long).mean()
        if (future_kline.ma_short.iloc[-2] - future_kline.ma_long.iloc[-2]) * \
            (main_kline.ma_short.iloc[-2] - main_kline.ma_long.iloc[-2]) < 0: 
            logger.critical('应当换合约，因为两合约持仓不一致，暂时不换！请检查!')
            return


        context.main_contract = future_contract
        res_close = manage_order(main_contract, 0)
        res_open = manage_order(future_contract, int(account.balance / context.vol_factor))
        t = time.time()
        
        logger.critical(f'换合约{main_contract} -> {future_contract}')
        
        while not res_open.is_finished() or not res_close.is_finished():
            api.wait_update(time.time() + 1)

        cfg['sma']['main_contract'] = future_contract  # 记录下来，避免下交易日又换回来
        with open('./config/Record_sma.json', 'w') as f:
            json.dump(cfg, f)
        

        write_signal(main_contract, 'sma', t, res_close.trades_df, True, quote_main.last_price)
        write_signal(future_contract, 'sma', t, res_open.trades_df, True, quote_future.last_price)
        kline = api.get_kline_serial(context.main_contract, 60, data_length=700)


def stop():
    global cfg
    cfg["sma"]["main_contract"] = context.main_contract 
    with open('./config/Record_sma.json', 'w') as f:
        json.dump(cfg, f)

try:
    # 登陆账户
    try:
        if cfg["sma"]["sim"]:
            api = TqApi(TqKq(), auth=TqAuth("18064114200", "86888196"))
            logger.critical('登录成功！sim')
        else:
            api = TqApi(TqAccount('J金元期货', '10111731', 'Yhlz0000'), auth=TqAuth("18120588798", "86888196"))
            logger.critical('登录成功！J金元期货')
            logger.debug('')
    except Exception as e:
            logger.error('problem with sign in!')
            raise e

    # region 订阅数据
    quote = api.get_quote("KQ.m@SHFE.rb")

    kline = api.get_kline_serial(context.main_contract, 60, data_length=700)

    account = api.get_account()
    position = api.get_position()
    trades = api.get_trade()
    orders = api.get_order()
    res = None
    # endregion

    # highlight  以目前几百单的量级，还不用操心算法参数可能互相矛盾的问题

    # 循环运行
    while True:
        now = time.localtime()
        api.wait_update(time.time() + 1)
        
        if context.leicang:
            if account.risk_ratio > 0.5:
                context.offect_rule = '今昨,开'
        
            elif account.risk_ratio < 0.5:  
                context.offect_rule = '昨,开'

        if api.is_changing(kline.iloc[-1], 'datetime'):
            logger.debug(f'{kline.tail(3)}')
            if context.check_mark:
                check_change_contract(api)
                context.check_mark = False
            kline['ma_short'] = kline['close'].rolling(context.short).mean()
            kline['ma_long'] = kline['close'].rolling(context.long).mean()
            vol = int(account.balance / context.vol_factor)
            if (kline.ma_short.iloc[-2] >= kline.ma_long.iloc[-2]) and (kline.ma_short.iloc[-3] < kline.ma_long.iloc[-3]):
                logger.critical(f"目标多头{vol}手")
                price = kline.close.iloc[-2]
                res = manage_order(context.main_contract, vol)
                t = time.time()
                stop()
            elif (kline.ma_short.iloc[-2] <= kline.ma_long.iloc[-2]) and (kline.ma_short.iloc[-3] > kline.ma_long.iloc[-3]):
                logger.critical(f"目标空头{vol}手")
                price = kline.close.iloc[-2]
                res = manage_order(context.main_contract, -vol)
                t = time.time()
                stop()

        if res and res.is_finished():
            write_signal(context.main_contract, 'sma', t, res.trades_df, True, price)
            res = None

        if now.tm_hour == 23 or now.tm_hour == 15:
            api.close()
            if now.tm_hour == 15:
                write_orders(orders)        # 记录每日成交持仓
                write_trades(trades)
            break

except Exception as e:
    logger.error('运行出现问题请立即检查！\n' + traceback.format_exc())

finally:
    stop()
    api.close()
    logger.critical('停止运行！')
